package com.cucn.ingmall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cucn.common.utils.PageUtils;
import com.cucn.common.utils.Query;
import com.cucn.ingmall.product.dao.CategoryDao;
import com.cucn.ingmall.product.entity.CategoryEntity;
import com.cucn.ingmall.product.service.CategoryBrandRelationService;
import com.cucn.ingmall.product.service.CategoryService;
import com.cucn.ingmall.product.vo.Catalog2VO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * @author KouChaoJie
 * @since 2021-11-17 16:27:04
 */
@Slf4j
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 查出所有分类以及子分类,以树形结构组装起来
     *
     * @return 以树形结构包装的所有分类
     */
    @Override
    public List<CategoryEntity> listWithTree() {
        //1.查出所有分类
        List<CategoryEntity> entities = baseMapper.selectList(null);

        //2.组装成父子的树形结构
        //2.1找出所有的一级分类
        //过滤得到一级分类
        List<CategoryEntity> level1Menus = entities.stream().filter(categoryEntity ->
                        categoryEntity.getParentCid() == 0)
                //设置子分类
                .map((menu) -> {
                    menu.setChildren(getChildrens(menu, entities));
                    return menu;
                })
                //对菜单进行排序
                .sorted((menu1, menu2) -> {
                    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
                })
                //返回集合
                .collect(Collectors.toList());

        return level1Menus;
    }

    /**
     * 找到当前菜单的子菜单
     *
     * @param root 当前菜单
     * @param all  所有菜单
     * @return 当前菜单的子菜单
     */
    private List<CategoryEntity> getChildrens(CategoryEntity root, List<CategoryEntity> all) {
        return all.stream().filter(categoryEntity ->
                        categoryEntity.getParentCid().equals(root.getCatId()))
                .map((categoryEntity) -> {
                    //1.找到子菜单
                    categoryEntity.setChildren(getChildrens(categoryEntity, all));
                    return categoryEntity;
                })
                //排序
                .sorted((menu1, menu2) -> {
                    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
                })
                .collect(Collectors.toList());
    }

    /**
     * 实现菜单的批量删除
     *
     * @param asList 需要删除的菜单id
     */
    @Override
    public void removeMenuByIds(List<Long> asList) {
        //KouTODO:2021/11/23 14:00 Kou 检查当前菜单是否被别处引用
        //物理删除,后续使用逻辑删除
        baseMapper.deleteBatchIds(asList);
    }

    /**
     * 根据第三级分类id查询出完整分类路径
     * [父/子/孙]
     *
     * @param catelogId 第三级分类id
     * @return 完整分类路径
     */
    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        List<Long> parentPath = findParentPath(catelogId, paths);
        //逆序转换,得到正确数据
        Collections.reverse(parentPath);

        return parentPath.toArray(new Long[3]);
    }

    /**
     * 查找catelogId对应的父分类的id
     *
     * @param catelogId 分类id
     * @param paths     收集数据的容器
     * @return 完整分类路径
     */
    private List<Long> findParentPath(Long catelogId, List<Long> paths) {
        //收集当前节点id
        paths.add(catelogId);
        CategoryEntity category = this.getById(catelogId);
        //如果还有父分类,递归查找
        if (category.getParentCid() != 0) {
            findParentPath(category.getParentCid(), paths);
        }
        return paths;
    }

    /**
     * 修改细节（关联表连带修改）
     * 缓存失效模式的使用
     * 同时进行多种缓存操作
     *
     * @param category 修改的分类信息
     */
    @Override
    @Caching(evict = {
            @CacheEvict(value = "category",key = "'selectLevelOne`Category'"),
            @CacheEvict(value = "category",key = "'getCatalogJson'")
    })
    @Transactional
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);

        if (!StringUtils.isEmpty(category.getName())) {
            //同步关联表信息
            categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
        }

        //KouTODO:2021/11/30 13:34 Kou 更新关联其他表
    }


    /**
     * 查询所有一级分类
     * 每一个需要缓存的数据都来指定要放在哪个名字的缓存（分区）按照业务类型分
     *
     * @return 一级分类集合
     */
    @Override
    @Cacheable(value = {"category"}, key = "#root.method.name",sync = true)
    public List<CategoryEntity> selectLevelOneCategory() {
        System.out.println();
        long l = System.currentTimeMillis();
        List<CategoryEntity> categoryOneList = this.baseMapper.selectLevelOneCategory();
        System.out.println(System.currentTimeMillis() - l + "selectLevelOneCategory");
        return categoryOneList;
    }

    /**
     * 查询二级三级分类
     * "一级分类id":[Catalog2VO]
     * 使用分布式锁,缓存,解决了最终一致性,最终方法,缓存注解
     *
     * @return "一级分类id":[List<Catalog2VO>]
     */
    @Override
    @Cacheable(value = {"category"}, key = "#root.methodName",sync = true)
    public Map<String, List<Catalog2VO>> getCatalogJson() {
        log.info("查询了数据库.....");

        List<CategoryEntity> categoryList = baseMapper.selectList(null);

        //先查出所有一级分类
        List<CategoryEntity> levelOneCategory = getChildCategory(categoryList, 0L);

        //封装一级分类
        Map<String, List<Catalog2VO>> map = levelOneCategory.stream().collect(Collectors.toMap(
                k -> k.getCatId().toString(),
                v -> {
                    //查询所有二级分类
                    List<CategoryEntity> categoryTwoList = getChildCategory(categoryList, v.getCatId());
                    List<Catalog2VO> catalog2VOList = null;

                    //封装VO数据
                    if (categoryTwoList != null) {
                        catalog2VOList = categoryTwoList.stream().map(l2 -> {
                            Catalog2VO catalog2VO = new Catalog2VO(l2.getParentCid().toString(), l2.getCatId().toString(), l2.getName(), null);

                            //封装三级分类
                            List<CategoryEntity> categoryThreeList = getChildCategory(categoryList, l2.getCatId());
                            List<Catalog2VO.Catalog3VO> catalog3List = categoryThreeList.stream().map(l3 -> {
                                Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(l3.getParentCid().toString(), l3.getCatId().toString(), l3.getName());
                                return catalog3VO;
                            }).collect(Collectors.toList());

                            catalog2VO.setCatalog3List(catalog3List);
                            return catalog2VO;
                        }).collect(Collectors.toList());
                    }

                    return catalog2VOList;
                }
        ));

        return map;
    }

    //KouTODO:2021/12/16 14:56 Kou 产生堆外内存溢出

    /**
     * 查询二级三级分类并与redis缓存结合使用
     * 给缓存中放json字符串,拿出的数据还要逆转成能用的对象类型
     * "一级分类id":[Catalog2VO]
     *
     * @return "一级分类id":[List<Catalog2VO>]
     */
    //@Override
    public Map<String, List<Catalog2VO>> getCatalogJson2() {
        /*
        1.空结果返回:解决缓存穿透
        2.设置过期时间(加随机值):解决缓存雪崩
        3.加锁:解决缓存击穿
         */
        //1.从缓存中查询数据
        String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");

        //2.判断缓存数据是否为空？
        if (StringUtils.isEmpty(catalogJSON)) {
            log.info("缓存不命中....查询数据库...");
            //从数据库查询数据
            Map<String, List<Catalog2VO>> catalogFromDb = getCatalogJsonFromDbWithRedissonLock();

            return catalogFromDb;
        }

        log.info("缓存命中....");
        //json字符串逆转为对象
        Map<String, List<Catalog2VO>> map = JSON.parseObject(catalogJSON,
                new TypeReference<Map<String, List<Catalog2VO>>>() {
                });
        return map;
    }

    /**
     * 使用分布式锁从数据库中查询二级三级分类
     * "一级分类id":[Catalog2VO]
     *
     * @return "一级分类id":[List<Catalog2VO>]
     */
    private Map<String, List<Catalog2VO>> getCatalogJsonFromDbWithRedissonLock() {
        //Redisson分布式锁
        //锁的名字,锁的粒度,越细越快
        //锁的粒度:具体缓存的是某个数据
        RLock lock = redissonClient.getLock("CatalogJson-lock");
        lock.lock();

        Map<String, List<Catalog2VO>> fromDb;
        try {
            fromDb = getCatalogJsonFromDb();
        } finally {
            lock.unlock();
        }

        return fromDb;
    }

    /**
     * 从数据库中查询二级三级分类
     * "一级分类id":[Catalog2VO]
     *
     * @return "一级分类id":[List<Catalog2VO>]
     */
    private Map<String, List<Catalog2VO>> getCatalogJsonFromDb() {
        /*
            1.将循环查表变为一次
            3.加锁:解决缓存击穿
                得到锁时,需要先查缓存,防止上一个线程已经查过数据库并设置缓存了
            */
        String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
        if (StringUtils.isNotEmpty(catalogJSON)) {
            //缓存不为空,直接返回
            Map<String, List<Catalog2VO>> map = JSON.parseObject(catalogJSON,
                    new TypeReference<Map<String, List<Catalog2VO>>>() {
                    });
            return map;
        }

        log.info("查询了数据库.....");

        List<CategoryEntity> categoryList = baseMapper.selectList(null);

        //先查出所有一级分类
        List<CategoryEntity> levelOneCategory = getChildCategory(categoryList, 0L);

        //封装一级分类
        Map<String, List<Catalog2VO>> map = levelOneCategory.stream().collect(Collectors.toMap(
                k -> k.getCatId().toString(),
                v -> {
                    //查询所有二级分类
                    List<CategoryEntity> categoryTwoList = getChildCategory(categoryList, v.getCatId());
                    List<Catalog2VO> catalog2VOList = null;

                    //封装VO数据
                    if (categoryTwoList != null) {
                        catalog2VOList = categoryTwoList.stream().map(l2 -> {
                            Catalog2VO catalog2VO = new Catalog2VO(l2.getParentCid().toString(), l2.getCatId().toString(), l2.getName(), null);

                            //封装三级分类
                            List<CategoryEntity> categoryThreeList = getChildCategory(categoryList, l2.getCatId());
                            List<Catalog2VO.Catalog3VO> catalog3List = categoryThreeList.stream().map(l3 -> {
                                Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(l3.getParentCid().toString(), l3.getCatId().toString(), l3.getName());
                                return catalog3VO;
                            }).collect(Collectors.toList());

                            catalog2VO.setCatalog3List(catalog3List);
                            return catalog2VO;
                        }).collect(Collectors.toList());
                    }

                    return catalog2VOList;
                }
        ));

        //将数据转换为json字符串
        String categoryJson = JSON.toJSONString(map);
        redisTemplate.opsForValue().set("catalogJSON", categoryJson, 1, TimeUnit.DAYS);

        return map;

    }

    /**
     * 查询分类子分类
     *
     * @param selectList 全部分类
     * @param parentCid  父分类id
     * @return 过滤出的分类集合
     */
    private List<CategoryEntity> getChildCategory(List<CategoryEntity> selectList, Long parentCid) {
        List<CategoryEntity> list = selectList.stream()
                .filter(item -> item.getParentCid().equals(parentCid)).collect(Collectors.toList());

        return list;
    }
}